using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;


namespace LightCAD.Three
{
    /**************************************************************
     *	Curved Path - a curve path is simply a array of connected
     *  curves, but retains the api of a curve
     **************************************************************/

    public class CurvePath<TVec> : Curve<TVec> where TVec : Vector, new()
    {
        public ListEx<Curve<TVec>> curves;
        public bool autoClose;
        public ListEx<double> cacheLengths;
        public CurvePath()
        {
            this.type = "CurvePath";
            this.curves = new ListEx<Curve<TVec>>();
            this.autoClose = false; // Automatically closes the path

        }

        public void add(Curve<TVec> curve)
        {

            this.curves.Push(curve);

        }

        public void closePath()
        {

            // Add a line curve if start and end of lines are not connected
            var startPoint = this.curves[0].getPoint(0);
            var endPoint = this.curves[this.curves.Length - 1].getPoint(1);

            if (!startPoint.Equals(endPoint))
            {
                if (this is Curve<Vector2>)
                {
                    var line = new LineCurve(endPoint as Vector2, startPoint as Vector2) as Curve<TVec>;
                    this.curves.Push(line);
                }
                else
                {
                    var line = new LineCurve3(endPoint as Vector3, startPoint as Vector3) as Curve<TVec>;
                    this.curves.Push(line);
                }
            }

        }

        // To get accurate point with reference to
        // entire path distance at time t,
        // following has to be done:

        // 1. Length of each sub path have to be known
        // 2. Locate and identify type of curve
        // 3. Get t for the curve
        // 4. Return curve.getPointAt(t')

        public override TVec getPoint(double t, TVec optionalTarget = null)
        {

            var d = t * this.getLength();
            var curveLengths = this.getCurveLengths();
            var i = 0;

            // To think about boundaries points.

            while (i < curveLengths.Length)
            {

                if (curveLengths[i] >= d)
                {

                    var diff = curveLengths[i] - d;
                    var curve = this.curves[i];

                    var segmentLength = curve.getLength();
                    var u = segmentLength == 0 ? 0 : 1 - diff / segmentLength;

                    return curve.getPointAt(u, optionalTarget);

                }

                i++;

            }

            return null;

            // loop where sum != 0, sum > d , sum+1 <d

        }

        // We cannot use the default THREE.Curve getPoint() with getLength() because in
        // THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath
        // getPoint() depends on getLength

        public override double getLength()
        {

            var lens = this.getCurveLengths();
            return lens[lens.Length - 1];

        }

        // cacheLengths must be recalculated.
        public override void updateArcLengths()
        {

            this.needsUpdate = true;
            this.cacheLengths = null;
            this.getCurveLengths();

        }

        // Compute lengths and cache them
        // We cannot overwrite getLengths() because UtoT mapping uses it.

        public ListEx<double> getCurveLengths()
        {

            // We use cache values if curves and cache array are same length

            if (this.cacheLengths != null && this.cacheLengths.Length == this.curves.Length)
            {

                return this.cacheLengths;

            }

            // Get length of sub-curve
            // Push sums into cached array

            var lengths = new ListEx<double>();
            double sums = 0;

            for (int i = 0, l = this.curves.Length; i < l; i++)
            {

                sums += this.curves[i].getLength();
                lengths.Push(sums);

            }

            this.cacheLengths = lengths;

            return lengths;

        }

        public override ListEx<TVec> getSpacedPoints(int divisions = 40)
        {

            var points = new ListEx<TVec>();

            for (var i = 0; i <= divisions; i++)
            {

                points.Push(this.getPoint(i / (double)divisions));

            }

            if (this.autoClose)
            {

                points.Push(points[0]);

            }

            return points;

        }

        public override ListEx<TVec> getPoints(int divisions = 12)
        {

            var points = new ListEx<TVec>();
            TVec last = null;
            var curves = this.curves;
            for (var i = 0; i < curves.Length; i++)
            {

                var curve = curves[i];
                var resolution = curve is EllipseCurve ? divisions * 2
                    : (curve is LineCurve || curve is LineCurve3) ? 1
                        : curve is SplineCurve ? divisions * (curve as SplineCurve).points.Length
                            : divisions;

                var pts = curve.getPoints(resolution);

                for (var j = 0; j < pts.Length; j++)
                {

                    var point = pts[j];

                    if (last != null && last.Equals(point)) continue; // ensures no consecutive points are duplicates

                    points.Push(point);
                    last = point;

                }

            }

            if (this.autoClose && points.Length > 1 && !points[points.Length - 1].Equals(points[0]))
            {

                points.Push(points[0]);

            }

            return points;

        }
        public override Curve<TVec> copy(Curve<TVec> source)
        {
            return copy(source as CurvePath<TVec>);
        }
        public CurvePath<TVec> copy(CurvePath<TVec> source)
        {

            base.copy(source);

            this.curves = new ListEx<Curve<TVec>>();

            for (int i = 0, l = source.curves.Length; i < l; i++)
            {

                var curve = source.curves[i];

                this.curves.Push(curve.clone());

            }

            this.autoClose = source.autoClose;

            return this;

        }
        public override Curve<TVec> clone()
        {
            return new CurvePath<TVec>().copy(this);
        }
        //    public override Curve<TVec> fromJSON(JObject json)
        //    {
        //        base.fromJSON(json);
        //        this.autoClose = json.GetBool("autoClose");
        //        this.curves =new JsArr<Curve<TVec>>();
        //        var jarr = json.GetArray("curves");

        //        for (int i = 0, l = jarr.Count; i < l; i++)
        //        {
        //            var jcurve = jarr[i] as JObject;
        //            var type= jcurve.GetString("type");
        //            if (this is CurvePath<Vector3>)
        //            {
        //                Curve<Vector3> curve = null;
        //                switch (type)
        //                {
        //                    case "CatmullRomCurve3":
        //                        curve = new CatmullRomCurve3();
        //                        break;
        //                    case "CubicBezierCurve3":
        //                        curve = new CubicBezierCurve3();
        //                        break;
        //                    case "LineCurve3":
        //                        curve = new LineCurve3();
        //                        break;
        //                    case "QuadraticBezierCurve3":
        //                        curve = new QuadraticBezierCurve3();
        //                        break;
        //                    default:
        //                        break;
        //                }
        //                this.curves.push((curve.fromJSON(jcurve) as Curve<TVec>));
        //            }
        //            else
        //            {

        //            }

        //        }
        //        return this;
        //    }
    }

}
